#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <DHT.h>
#include <SHA256.h>
#include <WiFiClientSecure.h>
#include <ESP8266HTTPClient.h>

String MyProductKey = "a14mSWkAoC4";
String MyProductSecret = "3OFp9VlyDnxpzoyt";
String MyDeviceName = "";
String MyDeviceSecret = "";
String MyRegionId = "cn-shanghai";

String alink_register_device()
{
  WiFiClientSecure tls;
  HTTPClient https;

  String randomString = String(random(9999));
  MyDeviceName = String(ESP.getChipId(), HEX);

  String signcontent = "deviceName" + MyDeviceName;
  signcontent += "productKey" + MyProductKey;
  signcontent += "random" + randomString;
  Serial.println(signcontent);

  String param = "productKey=" + MyProductKey;
  param += "&deviceName=" + MyDeviceName;
  param += "&random=" + randomString;
  param += "&sign=" + hmacsha256(signcontent, MyProductSecret);
  param += "&signMethod=hmacsha256";
  Serial.println(param);

  tls.setInsecure();
  https.begin(tls, "https://iot-auth.cn-shanghai.aliyuncs.com/auth/register/device");
  https.addHeader("Content-Type", "application/x-www-form-urlencoded");
  if (https.POST(param) != 200)
  {
    return MyDeviceSecret;
  }
  //{
  //  "code":200,
  //  "data":
  //  {
  //    "deviceName":"xxx",
  //    "deviceSecret":"xxx",
  //    "productKey":"xxx"
  //  },
  //  "message":"success"
  //}
  const size_t capacity = 2 * JSON_OBJECT_SIZE(3) + 130;
  DynamicJsonDocument doc(capacity);
  deserializeJson(doc, https.getString());
  Serial.println(https.getString());
  if (doc["code"] != 200)
  {
    return MyDeviceSecret;
  }
  MyDeviceSecret = doc["data"]["deviceSecret"].as<char*>();
  https.end();

  return MyDeviceSecret;
}

String alink_topic_prop_set(String& productKey, String& deviceName)
{
  return "/sys/" + productKey + "/" + deviceName + "/thing/service/property/set";
}

String alink_topic_prop_post(String& productKey, String& deviceName)
{
  return "/sys/" + productKey + "/" + deviceName + "/thing/event/property/post";
}

DHT sensor(D1, DHT11);
WiFiClient tcpclient;
PubSubClient mqtt(tcpclient);

//接收订阅的消息
void mqtt_callback(const char *topic, byte *payload, unsigned int length)
{
  Serial.println(topic);
  const size_t capacity = JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(4) + 90;
  DynamicJsonDocument doc(capacity);
  deserializeJson(doc, payload, length);
  int status = doc["params"]["status"];
  if (0 == status)
  {
    digitalWrite(16, HIGH);
  }
  if (1 == status)
  {
    digitalWrite(16, LOW);
  }
}

String aliyun_mqtt_server(String& productKey, String& regionId)
{
  return productKey + ".iot-as-mqtt." + regionId + ".aliyuncs.com";
}

String aliyun_mqtt_clientid(String& clientId, unsigned long timestamp)
{
  return clientId + "|securemode=3,signmethod=hmacsha256,timestamp=" + String(timestamp) + "|";
}

String aliyun_mqtt_username(String& productKey, String& deviceName)
{
  return deviceName + "&" + productKey;
}

#define SHA256HMAC_SIZE 32

String hmacsha256(const String &signcontent, const String &ds)
{
  byte hash[SHA256HMAC_SIZE];
  const char* hex = "0123456789ABCDEF";
  SHA256 sha256;

  const char *key = ds.c_str();
  size_t keySize = ds.length();

  sha256.resetHMAC(key, keySize);
  sha256.update((const byte *)signcontent.c_str(), signcontent.length());
  sha256.finalizeHMAC(key, keySize, hash, sizeof(hash));

  String sign = "";
  for (byte i = 0; i < SHA256HMAC_SIZE; ++i)
  {
    sign += hex[hash[i] >> 4];
    sign += hex[hash[i] & 0xf];
  }

  return sign;
}

String aliyun_mqtt_password(String& clientId, String& deviceName, String& productKey, unsigned long timestamp, String& deviceSecret)
{
  String signcontent = "clientId";
  signcontent += clientId;
  signcontent += "deviceName";
  signcontent += deviceName;
  signcontent += "productKey";
  signcontent += productKey;
  signcontent += "timestamp";
  signcontent += String(timestamp);

  return hmacsha256(signcontent, deviceSecret);
}

void setup()
{
  Serial.begin(115200);
  Serial.println();
  WiFi.mode(WIFI_STA);
  WiFi.begin("LIUYU", "12345678");
  while (!WiFi.isConnected())
  {
    delay(100);
  }
  Serial.println(WiFi.localIP());

  String ClientID = String(ESP.getChipId(), HEX);
  unsigned long ts = millis();

  alink_register_device();

  String server = aliyun_mqtt_server(MyProductKey, MyRegionId);
  String cid = aliyun_mqtt_clientid(MyDeviceName, ts);
  String user = aliyun_mqtt_username(MyProductKey, MyDeviceName);
  String pwd = aliyun_mqtt_password(MyDeviceName, MyDeviceName, MyProductKey, ts, MyDeviceSecret);

  mqtt.setServer(server.c_str(), 1883);
  mqtt.setCallback(mqtt_callback);

  Serial.println(server);
  Serial.println(cid);
  Serial.println(user);
  Serial.println(pwd);
  if (mqtt.connect(cid.c_str(), user.c_str(), pwd.c_str()))
  {
    Serial.println("MQTT broker connected");
    mqtt.subscribe(alink_topic_prop_set(MyProductKey, MyDeviceName).c_str());
  }
  else
  {
    Serial.println("MQTT broker connect failed: " + String(mqtt.state()));
  }

  pinMode(16, OUTPUT);
  sensor.begin();
}

int prev = 0;
int msgid = 0;

void loop()
{
  mqtt.loop();
  if (millis() - prev > 10000)
  {
    float temp = sensor.readTemperature();
    float humi = sensor.readHumidity();

    String msg;
    const size_t capacity = 2 * JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(4) + 100;
    DynamicJsonDocument doc(capacity);

    doc["id"] = String(msgid);
    doc["version"] = "1.0";
    doc["method"] = "thing.event.property.post";

    JsonObject params = doc.createNestedObject("params");
    JsonObject params_temp = params.createNestedObject("temperature");
    params_temp["value"] = temp;
    JsonObject params_humi = params.createNestedObject("humidity");
    params_humi["value"] = humi;
    serializeJson(doc, msg);

    Serial.println(msg);
    mqtt.publish(alink_topic_prop_post(MyProductKey, MyDeviceName).c_str(), msg.c_str());
    msgid++;
    prev = millis();
  }
}
